package com.opNaga.bpmp.swimlane;

import com.opNaga.bpmp.BPMPElement;
import com.opNaga.bpmp.artifact.Artifact;
import com.opNaga.bpmp.connectingObject.association.AssociationFlow;
import com.opNaga.bpmp.connectingObject.association.AssociationFlowSource;
import com.opNaga.bpmp.connectingObject.association.AssociationFlowTarget;
import com.opNaga.bpmp.connectingObject.messageFlow.MessageFlow;
import com.opNaga.bpmp.connectingObject.messageFlow.MessageFlowSource;
import com.opNaga.bpmp.connectingObject.messageFlow.MessageFlowTarget;
import com.opNaga.bpmp.connectingObject.sequenceFlow.SequenceFlowSource;
import com.opNaga.bpmp.connectingObject.sequenceFlow.SequenceFlowTarget;
import com.opNaga.bpmp.flowObject.event.EndEvent;
import com.opNaga.bpmp.flowObject.event.HasEnd;
import com.opNaga.bpmp.flowObject.event.HasStart;
import com.opNaga.bpmp.flowObject.event.StartEvent;

import java.util.*;

/**
 * User: Junyi BEI
 * Date: 19/01/2012
 * Time: 13:56
 */
public class Pool extends Swimlane implements AssociationFlowTarget, AssociationFlowSource, MessageFlowTarget, MessageFlowSource, HasStart, HasEnd {
  private Set<BPMPElement> lanes = new HashSet<BPMPElement>();
  private Set<BPMPElement> starts = new HashSet<BPMPElement>();
  private Set<BPMPElement> ends = new HashSet<BPMPElement>();
  private Set<BPMPElement> outwardMessageFlows = new HashSet<BPMPElement>();
  private Set<BPMPElement> inwardMessageFlows = new HashSet<BPMPElement>();
  private Set<BPMPElement> outwardAssociationFlows = new HashSet<BPMPElement>();
  private Set<BPMPElement> inwardAssociationFlows = new HashSet<BPMPElement>();

  public Pool() {
    super();
  }

  public Pool(String t) {
    super(t);
  }

  @Override
  public String getType() {
    return "Pool";
  }

  @Override
  public boolean destroy() {
    Iterator<BPMPElement> omfIt = iterateOutwardMessageFlows();
    while(omfIt.hasNext()) {
      BPMPElement be = omfIt.next();
      if(!be.destroy()) {
        return standardMessage(810, be);
      }
    }
    Iterator<BPMPElement> imfIt = iterateInwardMessageFlows();
    while(imfIt.hasNext()) {
      BPMPElement be = imfIt.next();
      if(!be.destroy()) {
        return standardMessage(840, be);
      }
    }
    Iterator<BPMPElement> lIt = iterateLanes();
    while(lIt.hasNext()) {
      BPMPElement l = lIt.next();
      if(!l.destroy()) {
        return standardMessage(702, l);
      }
    }
    Iterator<BPMPElement> oaIt = iterateOutwardAssociationFlows();
    while(oaIt.hasNext()) {
      BPMPElement be = oaIt.next();
      if(!be.destroy()) {
        return standardMessage(800, be);
      }
    }
    Iterator<BPMPElement> iaIt = iterateInwardAssociationFlows();
    while(iaIt.hasNext()) {
      BPMPElement be = iaIt.next();
      if(!be.destroy()) {
        return standardMessage(801, be);
      }
    }
    starts.clear();
    ends.clear();
    return super.destroy();
  }

  public boolean addElement(BPMPElement be) {
    if(lanes.size() > 0) {
      return standardMessage(555, be);
    }
    if(!Containable.class.isAssignableFrom(be.getClass())) {
      return standardMessage(550, be);
    }
    if(!((Containable) be).setContainer(this)) {
      return standardMessage(551, be);
    }
    if(be instanceof StartEvent) {
      if(!registerStart(be)) {
        return standardMessage(553, be);
      }
    }
    if(be instanceof EndEvent) {
      if(!registerEnd(be)) {
        return standardMessage(554, be);
      }
    }
    if(!elements.add(be)) {
      return standardMessage(552, be);
    }
    return standardMessage(590, be);
  }

  public boolean removeElement(BPMPElement be) {
    if(!(be instanceof Containable)) {
      return standardMessage(560, be);
    }
    if(!((Containable) be).setContainer(null)) {
      return standardMessage(561, be);
    }
    if(be instanceof StartEvent) {
      if(!deregisterStart(be)) {
        return standardMessage(563, be);
      }
    }
    if(be instanceof EndEvent) {
      if(!deregisterEnd(be)) {
        return standardMessage(564, be);
      }
    }
    if(!elements.remove(be)) {
      return standardMessage(562, be);
    }
    return standardMessage(591, be);
  }

  public BPMPElement getRoot() {
    return this;
  }

  public BPMPElement getParent() {
    return null;
  }

  public boolean isRoot() {
    return true;
  }

  public boolean addLane(BPMPElement laneElement) {
    if(!Lane.class.isAssignableFrom(laneElement.getClass())) {
      return standardMessage(500, laneElement);
    }
    if(elements.size() > 0) {
      return standardMessage(501, laneElement);
    }
    if(!((Lane) laneElement).setParent(this)) {
      return standardMessage(502, laneElement);
    }
    if(!lanes.add(laneElement)) {
      return standardMessage(503, laneElement);
    }
    return standardMessage(540, laneElement);
  }

  public boolean includes(BPMPElement be) {
    return lanes.contains(be);
  }

  public boolean removeLane(BPMPElement laneElement) {
    if(!(laneElement instanceof Lane)) {
      return standardMessage(510, laneElement);
    }
    if(!((Lane) laneElement).setParent(null)) {
      return standardMessage(511, laneElement);
    }
    if(!lanes.remove(laneElement)) {
      return standardMessage(512, laneElement);
    }
    return standardMessage(541, laneElement);
  }

  public Iterator<BPMPElement> iterateLanes() {
    return new ArrayList<BPMPElement>(lanes).iterator();
  }

  public boolean isLaneEmpty() {
    return lanes.isEmpty();
  }

  public int laneSize() {
    return lanes.size();
  }

  public boolean registerStart(BPMPElement be) {
    if(!StartEvent.class.isAssignableFrom(be.getClass())) {
      return standardMessage(400, be);
    }
    if(!starts.add(be)) {
      return standardMessage(401, be);
    }
    return standardMessage(490, be);
  }

  public boolean deregisterStart(BPMPElement be) {
    if(!StartEvent.class.isAssignableFrom(be.getClass())) {
      return standardMessage(410, be);
    }
    if(!starts.remove(be)) {
      return standardMessage(411, be);
    }
    return standardMessage(491, be);
  }

  public Collection<BPMPElement> getAtomicElements() {
    Queue<BPMPElement> containers = new LinkedList<BPMPElement>();
    Collection<BPMPElement> atomicElements = new HashSet<BPMPElement>();
    containers.add(this);
    Iterator<BPMPElement> laneIt = iterateLanes();
    while(laneIt.hasNext()) {
      containers.add(laneIt.next());
    }
    while(!containers.isEmpty()) {
      BPMPElement container = containers.poll();
      Iterator<BPMPElement> elementIt = ((Container) container).iterateElements();
      while(elementIt.hasNext()) {
        BPMPElement element = elementIt.next();
        if(Container.class.isAssignableFrom(element.getClass())) {
          containers.add(element);
        } else {
          atomicElements.add(element);
        }
      }
    }
    return atomicElements;
  }

  public Iterator<BPMPElement> iterateAtomicElements() {
    return getAtomicElements().iterator();
  }

  public Iterator<BPMPElement> iterateStarts() {
    if(isStartEmpty()) {
      Collection<BPMPElement> defaultStarts = new HashSet<BPMPElement>();
      if(!isEmpty()) {
        Iterator<BPMPElement> elementIt = iterateElements();
        while(elementIt.hasNext()) {
          BPMPElement element = elementIt.next();
          if(SequenceFlowTarget.class.isAssignableFrom(element.getClass()) && ((SequenceFlowTarget) element).isInwardSequenceFlowEmpty()) {
            defaultStarts.add(element);
          }
        }
      } else if(!isLaneEmpty()) {
        Iterator<BPMPElement> laneIt = iterateLanes();
        while(laneIt.hasNext()) {
          BPMPElement lane = laneIt.next();
          Iterator<BPMPElement> elementIt = ((Lane) lane).iterateElements();
          while(elementIt.hasNext()) {
            BPMPElement element = elementIt.next();
            if(SequenceFlowSource.class.isAssignableFrom(element.getClass()) && ((SequenceFlowSource) element).isOutwardSequenceFlowEmpty()) {
              defaultStarts.add(element);
            }
          }
        }
      }
      return defaultStarts.iterator();
    }
    return new ArrayList<BPMPElement>(starts).iterator();
  }

  public boolean isStartEmpty() {
    return starts.isEmpty();
  }

  public boolean isOutwardAssociationFlowEmpty() {
    return outwardAssociationFlows.isEmpty();
  }

  public int outwardAssociationFlowSize() {
    return outwardAssociationFlows.size();
  }

  public boolean isInwardAssociationFlowEmpty() {
    return inwardAssociationFlows.isEmpty();
  }

  public int inwardAssociationFlowSize() {
    return inwardAssociationFlows.size();
  }

  public boolean isOutwardMessageFlowEmpty() {
    return outwardMessageFlows.isEmpty();
  }

  public int outwardMessageFlowSize() {
    return outwardMessageFlows.size();
  }

  public boolean isInwardMessageFlowEmpty() {
    return inwardMessageFlows.isEmpty();
  }

  public int inwardMessageFlowSize() {
    return inwardMessageFlows.size();
  }

  public int startSize() {
    return starts.size();
  }

  public boolean registerEnd(BPMPElement be) {
    if(!EndEvent.class.isAssignableFrom(be.getClass())) {
      return standardMessage(420, be);
    }
    if(!ends.add(be)) {
      return standardMessage(421, be);
    }
    return standardMessage(492, be);
  }

  public boolean deregisterEnd(BPMPElement be) {
    if(!EndEvent.class.isAssignableFrom(be.getClass())) {
      return standardMessage(430, be);
    }
    if(!starts.remove(be)) {
      return standardMessage(431, be);
    }
    return standardMessage(493, be);
  }

  public Iterator<BPMPElement> iterateEnds() {
    if(isEndEmpty()) {
      Collection<BPMPElement> defaultEnds = new HashSet<BPMPElement>();
      if(!isEmpty()) {
        Iterator<BPMPElement> elementIt = iterateElements();
        while(elementIt.hasNext()) {
          BPMPElement element = elementIt.next();
          if(SequenceFlowSource.class.isAssignableFrom(element.getClass()) && ((SequenceFlowSource) element).isOutwardSequenceFlowEmpty()) {
            defaultEnds.add(element);
          }
        }
      } else if(!isLaneEmpty()) {
        Iterator<BPMPElement> laneIt = iterateLanes();
        while(laneIt.hasNext()) {
          BPMPElement lane = laneIt.next();
          Iterator<BPMPElement> elementIt = ((Lane) lane).iterateElements();
          while(elementIt.hasNext()) {
            BPMPElement element = elementIt.next();
            if(SequenceFlowSource.class.isAssignableFrom(element.getClass()) && ((SequenceFlowSource) element).isOutwardSequenceFlowEmpty()) {
              defaultEnds.add(element);
            }
          }
        }
      }
      return defaultEnds.iterator();
    }
    return new ArrayList<BPMPElement>(ends).iterator();
  }

  public boolean isEndEmpty() {
    return ends.isEmpty();
  }

  public int endSize() {
    return ends.size();
  }

  public boolean addOutwardMessageFlow(BPMPElement be) {
    if(!(be instanceof MessageFlow)) {
      return standardMessage(110, be);
    }
    if(!outwardMessageFlows.add(be)) {
      return standardMessage(111, be);
    }
    return standardMessage(191, be);
  }

  public boolean removeOutwardMessageFlow(BPMPElement be) {
    if(!(be instanceof MessageFlow)) {
      return standardMessage(210, be);
    }
    if(!outwardMessageFlows.remove(be)) {
      return standardMessage(211, be);
    }
    return standardMessage(291, be);
  }

  public Iterator<BPMPElement> iterateOutwardMessageFlows() {
    return new ArrayList<BPMPElement>(outwardMessageFlows).iterator();
  }

  public boolean addInwardMessageFlow(BPMPElement be) {
    if(!(be instanceof MessageFlow)) {
      return standardMessage(140, be);
    }
    if(!inwardMessageFlows.add(be)) {
      return standardMessage(141, be);
    }
    return standardMessage(194, be);
  }

  public boolean removeInwardMessageFlow(BPMPElement be) {
    if(!(be instanceof MessageFlow)) {
      return standardMessage(240, be);
    }
    if(!inwardMessageFlows.remove(be)) {
      return standardMessage(241, be);
    }
    return standardMessage(294, be);
  }

  public Iterator<BPMPElement> iterateInwardMessageFlows() {
    return new ArrayList<BPMPElement>(inwardMessageFlows).iterator();
  }

  public boolean addOutwardAssociationFlow(BPMPElement be) {
    if(!(be instanceof AssociationFlow)) {
      return standardMessage(120, be);
    }
    if(!outwardAssociationFlows.add(be)) {
      return standardMessage(121, be);
    }
    return standardMessage(192, be);
  }

  public boolean removeOutwardAssociationFlow(BPMPElement be) {
    if(!(be instanceof AssociationFlow)) {
      return standardMessage(220, be);
    }
    if(!outwardAssociationFlows.remove(be)) {
      return standardMessage(221, be);
    }
    return standardMessage(292, be);
  }

  public Iterator<BPMPElement> iterateOutwardAssociationFlows() {
    return new ArrayList<BPMPElement>(outwardAssociationFlows).iterator();
  }

  public boolean addInwardAssociationFlow(BPMPElement be) {
    if(!(be instanceof AssociationFlow)) {
      return standardMessage(150, be);
    }
    if(!inwardAssociationFlows.add(be)) {
      return standardMessage(151, be);
    }
    return standardMessage(195, be);
  }

  public boolean removeInwardAssociationFlow(BPMPElement be) {
    if(!(be instanceof AssociationFlow)) {
      return standardMessage(250, be);
    }
    if(!inwardAssociationFlows.remove(be)) {
      return standardMessage(251, be);
    }
    return standardMessage(295, be);
  }

  public Iterator<BPMPElement> iterateInwardAssociationFlows() {
    return new ArrayList<BPMPElement>(inwardAssociationFlows).iterator();
  }

  public BPMPElement linkTo(BPMPElement be) {
    if(AssociationFlowTarget.class.isAssignableFrom(be.getClass()) && Artifact.class.isAssignableFrom(be.getClass())) {
      BPMPElement af;
      if(manager != null) {
        if(be.getManager() == manager) {
          af = manager.createInstance("af", "-s=" + name, "-t=" + be.getName());
          if(af != null) {
            standardMessage(910, be, af);
            return af;
          } else {
            standardMessage(903, be);
            return null;
          }
        } else {
          standardMessage(902, be);
          return null;
        }
      } else {
        af = new AssociationFlow();
        if(!((AssociationFlow) af).setTarget(be)) {
          af.destroy();
          standardMessage(900, be, af);
          return null;
        }
        if(!((AssociationFlow) af).setSource(this)) {
          af.destroy();
          standardMessage(901, be, af);
          return null;
        }
        if(standardMessage(911, be, af)) {
          return af;
        }
      }
    }
    if(MessageFlowTarget.class.isAssignableFrom(be.getClass()) && ((MessageFlowTarget) be).getRoot() != root) {
      BPMPElement mf;
      if(manager != null) {
        if(be.getManager() == manager) {
          mf = manager.createInstance("mf", "-s=" + name, "-t=" + be.getName());
          if(mf != null) {
            standardMessage(910, be, mf);
            return mf;
          } else {
            standardMessage(903, be);
            return null;
          }
        } else {
          standardMessage(902, be);
          return null;
        }
      } else {
        mf = new MessageFlow();
        if(!((MessageFlow) mf).setTarget(be)) {
          mf.destroy();
          standardMessage(900, be, mf);
          return null;
        }
        if(!((MessageFlow) mf).setSource(this)) {
          mf.destroy();
          standardMessage(901, be, mf);
          return null;
        }
        if(standardMessage(911, be, mf)) {
          return mf;
        }
      }
    }
    standardMessage(904, be);
    return null;
  }

  public BPMPElement getLinkTo(BPMPElement be) {
    if(AssociationFlowTarget.class.isAssignableFrom(be.getClass()) && Artifact.class.isAssignableFrom(be.getClass())) {
      Iterator<BPMPElement> oafIt = iterateOutwardAssociationFlows();
      while(oafIt.hasNext()) {
        BPMPElement oaf = oafIt.next();
        if(((AssociationFlow) oaf).getTarget() == be) {
          if(standardMessage(950, be, oaf)) {
            return oaf;
          } else {
            return null;
          }
        }
      }
      standardMessage(940, be);
      return null;
    }
    if(MessageFlowTarget.class.isAssignableFrom(be.getClass()) && ((MessageFlowTarget) be).getRoot() != root) {
      Iterator<BPMPElement> omfIt = iterateOutwardMessageFlows();
      while(omfIt.hasNext()) {
        BPMPElement omf = omfIt.next();
        if(((MessageFlow) omf).getTarget() == be) {
          standardMessage(950, be, omf);
          return omf;
        }
      }
      standardMessage(940, be);
      return null;
    }
    standardMessage(941, be);
    return null;
  }

  public BPMPElement linkFrom(BPMPElement be) {
    if(AssociationFlowSource.class.isAssignableFrom(be.getClass()) && Artifact.class.isAssignableFrom(be.getClass())) {
      BPMPElement af;
      if(manager != null) {
        if(be.getManager() == manager) {
          af = manager.createInstance("af", "-s=" + be.getName(), "-t=" + name);
          if(af != null) {
            standardMessage(930, be, af);
            return af;
          } else {
            standardMessage(923, be);
            return null;
          }
        } else {
          standardMessage(922, be);
          return null;
        }
      } else {
        af = new AssociationFlow();
        if(!((AssociationFlow) af).setTarget(this)) {
          af.destroy();
          standardMessage(920, be, af);
          return null;
        }
        if(!((AssociationFlow) af).setSource(be)) {
          af.destroy();
          standardMessage(921, be, af);
          return null;
        }
        if(standardMessage(931, be, af)) {
          return af;
        }
      }
    }
    if(MessageFlowSource.class.isAssignableFrom(be.getClass()) && ((MessageFlowSource) be).getRoot() != root) {
      BPMPElement mf;
      if(manager != null) {
        if(be.getManager() == manager) {
          mf = manager.createInstance("mf", "-s=" + be.getName(), "-t=" + name);
          if(mf != null) {
            standardMessage(930, be, mf);
            return mf;
          } else {
            standardMessage(923, be);
            return null;
          }
        } else {
          standardMessage(922, be);
          return null;
        }
      } else {
        mf = new MessageFlow();
        if(!((MessageFlow) mf).setTarget(this)) {
          mf.destroy();
          standardMessage(920, be, mf);
          return null;
        }
        if(!((MessageFlow) mf).setSource(be)) {
          mf.destroy();
          standardMessage(921, be, mf);
          return null;
        }
        if(standardMessage(931, be, mf)) {
          return mf;
        }
      }
    }
    standardMessage(924, be);
    return null;
  }

  public BPMPElement getLinkFrom(BPMPElement be) {
    if(AssociationFlowSource.class.isAssignableFrom(be.getClass()) && Artifact.class.isAssignableFrom(be.getClass())) {
      Iterator<BPMPElement> iafIt = iterateInwardAssociationFlows();
      while(iafIt.hasNext()) {
        BPMPElement iaf = iafIt.next();
        if(((AssociationFlow) iaf).getSource() == be) {
          if(standardMessage(970, be, iaf)) {
            return iaf;
          } else {
            return null;
          }
        }
      }
      standardMessage(960, be);
      return null;
    }
    if(MessageFlowSource.class.isAssignableFrom(be.getClass()) && ((MessageFlowSource) be).getRoot() != root) {
      Iterator<BPMPElement> imfIt = iterateInwardMessageFlows();
      while(imfIt.hasNext()) {
        BPMPElement imf = imfIt.next();
        if(((MessageFlow) imf).getSource() == be) {
          if(standardMessage(970, be, imf)) {
            return imf;
          } else {
            return null;
          }
        }
      }
      standardMessage(960, be);
      return null;
    }
    standardMessage(961, be);
    return null;
  }
}
